﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Kds.Xml.Expat;
using SAX = Org.System.Xml.Sax;


namespace BetterXml
{
    /// <summary>
    /// convert from expat-wrapper.cpp
    /// </summary>
    internal class ExpatWrapper : SAX.IErrorHandler, SAX.IContentHandler, SAX.IDtdHandler, SAX.ILexicalHandler, SAX.IDeclHandler //, SAX.IEntityResolver
    {
        private bool NoError = true;
        private Dictionary<string, string> UriMap = new Dictionary<string, string>();

        public ExpatWrapper()
        {
        }

        public bool Parse(string xmlFile)
        {
            var reader = new ExpatReader();
            reader.SetFeature(SAX.Constants.NamespacesFeature, true);
            reader.ErrorHandler = this;
            reader.ContentHandler = this;
            reader.DtdHandler = this;
            reader.LexicalHandler = this;
            reader.DeclHandler = this;
            //reader.EntityResolver = this;

            // The xpath evaluator requires the XML be in the format of an InputSource
            try
            {
                SAX.InputSource inputXml = new SAX.InputSource(xmlFile);
                reader.Parse(inputXml);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.Message);
                return false;
            }
            return NoError;
        }

        public void Suspend()
        {

        }

        public void Abort()
        {

        }

        public void Resume()
        {

        }

        private static KeyValuePair<string, string> GetNamespaceDataPair(string value)
        {
            int loc = value.IndexOf('#', 0); //character shouldn't be hardcoded in

            if (loc > 0)
            {
                return new KeyValuePair<string, string>(value.Substring(0, loc), value.Substring(loc + 1));
            }
            else
            {
                return new KeyValuePair<string, string>("None", value);
            }
        }


        #region Implement Org.System.Xml.Sax.IErrorHandler

        public void Error(SAX.ParseError error)
        {
            NoError = false;
            Trace.TraceError("[Error] " + error.Message);
        }

        public void FatalError(SAX.ParseError error)
        {
            NoError = false;
            Trace.TraceError("[Error] " + error.Message);
        }

        public void Warning(SAX.ParseError error)
        {
            Trace.TraceError("[Warning] " + error.Message);
        }

        #endregion

        #region Implement Org.System.Xml.Sax.IContentHandler

        public void StartDocument()
        {
            Trace.TraceInformation("[Information] StartDocument");

            (new XIRDataObject("document", "begin")).Print();
        }

        public void EndDocument()
        {
            Trace.TraceInformation("[Information] EndDocument");

            (new XIRDataObject("document", "end")).Print();
        }

        public void StartElement(string uri, string localName, string qName, SAX.IAttributes atts)
        {
            Trace.TraceInformation("[Information] StartElement: " + localName + " - " + qName);

            KeyValuePair<string, string> pair = GetNamespaceDataPair(localName);
            XIRDataObject xir = new XIRDataObject("element", "begin");
            xir.SetVerbatim("attributes", atts.Length.ToString(CultureInfo.InvariantCulture));
            xir.SetVerbatim("ns", pair.Key);
            xir.SetVerbatim("name", pair.Value);
            xir.Print();

            //loop over attributes and print them out
            for (int i = 0; i < atts.Length; ++i)
            {
                string attribute = atts.GetLocalName(i);
                string value = atts.GetValue(i);

                //add check in here to see if we're talking about element xmlns?

                KeyValuePair<string, string> dataPair = GetNamespaceDataPair(attribute);

                XIRDataObject attrObject = new XIRDataObject("a", "singleton");
                attrObject.SetVerbatim("ns", dataPair.Key == "None" ? dataPair.Key : pair.Key);
                attrObject.SetVerbatim("name", dataPair.Value);
                attrObject.SetVerbatim("value", value);
                attrObject.Print();
            }

        }

        public void EndElement(string uri, string localName, string qName)
        {
            Trace.TraceInformation("[Information] EndElement: " + localName + " - " + qName);

            KeyValuePair<string, string> pair = GetNamespaceDataPair(localName);

            XIRDataObject xir = new XIRDataObject("element", "end");
            xir.SetVerbatim("ns", pair.Key);
            xir.SetVerbatim("name", pair.Value);
            xir.Print();
        }


        public void Characters(char[] ch, int start, int length)
        {
            string data = (new string(ch)).Substring(start, length);

            Trace.TraceInformation("[Information] Characters: " + data);

            XIRDataObject xir = new XIRDataObject("characters", null);
            xir.SetBase64("cdata", data);
            xir.Print();
        }

        public void ProcessingInstruction(string target, string data)
        {
            Trace.TraceInformation("[Information] ProcessingInstruction: " + target + " - " + data);

            //XIRDataObject xir = new XIRDataObject("pi", null);
            //xir.SetVerbatim("name", data); //Is this right??
            //xir.SetVerbatim("target", target);
            //xir.Print();
        }

        //namespace
        public void StartPrefixMapping(string prefix, string uri)
        {
            Trace.TraceInformation("[Information] StartPrefixMapping: " + prefix + " - " + uri);

            XIRDataObject xir = new XIRDataObject("prefix", "begin");
            prefix = string.IsNullOrEmpty(prefix) ? "None" : prefix;
            uri = string.IsNullOrEmpty(uri) ? "None" : uri;
            xir.SetVerbatim("prefix", prefix);
            xir.SetVerbatim("uri", uri);
            xir.Print();

            if (!UriMap.ContainsKey(prefix))
            {
                UriMap.Add(prefix, uri);
            }
        }

        public void EndPrefixMapping(string prefix)
        {
            Trace.TraceInformation("[Information] EndPrefixMapping: " + prefix);

            prefix = string.IsNullOrEmpty(prefix) ? "None" : prefix;
            XIRDataObject xir = new XIRDataObject("prefix", "end");
            xir.SetVerbatim("prefix", prefix);
            xir.SetVerbatim("uri", UriMap[prefix]);
            xir.Print();

            UriMap.Remove(prefix);

        }

        public void IgnorableWhitespace(char[] ch, int start, int length)
        {
            Trace.TraceInformation("[Information] IgnorableWhitespace: " + new string(ch));
        }

        public void SetDocumentLocator(SAX.ILocator locator)
        {
            Trace.TraceInformation("[Information] SetDocumentLocator: " + locator.SystemId);
        }

        public void SkippedEntity(string name)
        {
            Trace.TraceInformation("[Information] SkippedEntity: " + name);
        }

        #endregion

        #region Implement Org.System.Xml.Sax.IDtdHandler

        public void NotationDecl(string name, string publicId, string systemId)
        {
            Trace.TraceInformation("[Information] NotationDecl: " + name + publicId + systemId);
        }

        public void UnparsedEntityDecl(string name, string publicId, string systemId, string notationName)
        {
            Trace.TraceInformation("[Information] UnparsedEntityDecl: " + name + publicId + systemId + notationName);
        }

        #endregion

        #region Implement Org.System.Xml.Sax.ILexicalHandler

        public void Comment(char[] ch, int start, int length)
        {
            Trace.TraceInformation("[Information] Comment: " + new string(ch));
        }

        public void EndCData()
        {
            Trace.TraceInformation("[Information] EndCData");
        }

        public void EndDtd()
        {
            Trace.TraceInformation("[Information] EndDtd");
        }

        public void EndEntity(string name)
        {
            Trace.TraceInformation("[Information] EndEntity: " + name);
        }

        public void StartCData()
        {
            Trace.TraceInformation("[Information] StartCData");
        }

        public void StartDtd(string name, string publicId, string systemId)
        {
            Trace.TraceInformation("[Information] StartDtd: " + name + publicId + systemId);
        }

        public void StartEntity(string name)
        {
            Trace.TraceInformation("[Information] StartEntity: " + name);
        }

        #endregion

        #region Implement Org.System.Xml.Sax.IEntityResolver

        //public SAX.InputSource GetExternalSubset(string name, string baseUri)
        //{
        //    throw new NotImplementedException("GetExternalSubset");
        //}

        //public SAX.InputSource ResolveEntity(string name, string publicId, string baseUri, string systemId)
        //{
        //    throw new NotImplementedException("ResolveEntity");
        //}

        #endregion

        #region Implement Org.System.Xml.Sax.IDeclHandler

        public void AttributeDecl(string eName, string aName, string aType, string mode, string aValue)
        {
            Trace.TraceInformation("[Information] AttributeDecl: " + eName + aName + aType + mode + aValue);
        }

        public void ElementDecl(string name, string model)
        {
            Trace.TraceInformation("[Information] ElementDecl: " + name + model);
        }

        public void ExternalEntityDecl(string name, string publicId, string systemId)
        {
            Trace.TraceInformation("[Information] ExternalEntityDecl: " + name + publicId + systemId);
        }

        public void InternalEntityDecl(string name, string value)
        {
            Trace.TraceInformation("[Information] InternalEntityDecl: " + name + value);
        }


        #endregion

    }
}
